﻿// Колесов 
using System;
using System.Collections.Generic;
using Transfer.DataLayer;
using Transfer.DataLayer.LAO;

namespace Transfer
{
    /// <summary> Класс выполняет запись документов в базу ИС-ПРО
    /// </summary>
    public class LaoTradeDoc
    {
        public LaoDataSet laoDS;

        /// <summary> Rcd прейскуранта сбыта по умолчанию
        /// </summary>
        public int DefaulPriceRcd
        {
            get { return _defaulPriceRcd; }
            set { _defaulPriceRcd = value; }
        }
        private int _defaulPriceRcd = 0;

        /// <summary> Список Rcd прейскурантов для возврата поставшику
        /// </summary>
        public List<int> DefaultBuyPriceRcd
        {
            get { return _defaultBuyPriceRcd; }
            set { _defaultBuyPriceRcd = value; }
        }
        private List<int> _defaultBuyPriceRcd;

        public LaoTradeDoc(int defPrice, List<int> defBuyPrice)
        {
            DefaulPriceRcd = defPrice;
            DefaultBuyPriceRcd = defBuyPrice;
        }

        /// <summary> Функция возвращает отформатированное значение полученного номера.
        /// </summary>
        /// <param name="rcdNum">Числовое значение номера</param>
        /// <returns>Строковое значение номера</returns>
        public string GetFmtNumDoc(int rcdNum)
        {
            return rcdNum.ToString().PadLeft(8, '0');
        }
        public string GetFmtNumDoc(string Num)
        {
            return Num.Trim().PadLeft(8, '0');
        }

        /// <summary> Запись одного документа в базу ИС-ПРО
        /// </summary>
        /// <param name="row">Заголовочная запись входного документа</param>
        /// <param name="cddoc">Код создаваемого документа</param>
        /// <param name="defPrice">Rcd прейскуранта по умолчанию для сбыта</param>
        /// <param name="defBuyPrice">Список прейскурантов для возврата поставщику</param>
        public void AddDoc(InputDocDS.DocHeaderRow row, CdDoc cddoc)
        {
            LaoTradeDocPrm docPrm = new LaoTradeDocPrm(cddoc);  // Получим константные параметры документа

            laoDS.TrdDoc.Append();

            #region ["Заполнение шапки документа"]
            laoDS.TrdDoc.SetInteger("CdAct", docPrm.Cd_Act);        // Код действия
            laoDS.TrdDoc.SetInteger("CdDoc", docPrm.Cd_Doc);        // Код документа
            laoDS.TrdDoc.SetDate("Dat", row.date_doc);              // Дата документа
            laoDS.TrdDoc.SetDate("DatPer", row.date_doc.AddDays(-row.date_doc.Day + 1));  // Дата периода, первое число месяца
            string snmr = GetFmtNumDoc(row.num_doc);
            laoDS.TrdDoc.Set("Nmr", snmr);                          // Номер документа внутренний
            laoDS.TrdDoc.Set("NmrExt", snmr);                       // Номер документа внешний

            laoDS.TrdDoc.SetInteger("Mov", docPrm.Mov);             // Направление движения
            laoDS.TrdDoc.SetInteger("Type", docPrm.Type);           // Тип документа
            laoDS.TrdDoc.SetInteger("Stt", docPrm.Stt);             // Статус

            laoDS.TrdDoc.SetInteger("JrnRcd", laoDS.Jrn.GetRcdByCode(row.jrn_cd, cddoc));   // Код журнала
            KagPrm kagPrm = laoDS.KAG.GetPrmByCode(row.gpl_cd);
            // Для приходов и возвратов поставщику всегда указывается поставщик ЧГМК - из настроек.
            // Поэтому подменим в параметрах контрагента данные плательщика.
            if (cddoc == CdDoc.ReversIncomBill || cddoc == CdDoc.IncomBill)
            {
                KagPrm tmpKag = laoDS.KAG.GetPrmByCode(Properties.Settings.Default.CodePost);
                kagPrm.RcdPlat = tmpKag.RcdPlat;
                kagPrm.Nm = tmpKag.Nm;
            }

            laoDS.TrdDoc.SetInteger("GplRcd", kagPrm.Rcd);          // Rcd грузополучателя
            laoDS.TrdDoc.SetInteger("KAgRcd", kagPrm.RcdPlat);      // Rcd плательщика
            laoDS.TrdDoc.Set("KAgId", kagPrm.Cd);                   // Код плательщика
            laoDS.TrdDoc.Set("KAgIdNm", kagPrm.Nm);                 // Наименование плательщика
            laoDS.TrdDoc.Set("SlfSch", laoDS.Firm.GetSchRcdByCode(""));    // Rcd расчетного счета предприятия
            laoDS.TrdDoc.SetInteger("KAgSch", laoDS.KAG.GetSchRcdByCode(kagPrm.RcdPlat,"")); // Rcd расчетного счета плательщика

            int rcdAdr;
            rcdAdr = laoDS.KAG.GetAdrRcd(kagPrm.RcdPlat, LaoKAG.AdrType.Pay);  // Адрес оплаты
            laoDS.TrdDoc.SetInteger("KAgAdr", (rcdAdr == 0) ? 1 : rcdAdr);
            rcdAdr = laoDS.KAG.GetAdrRcd(kagPrm.RcdPlat, LaoKAG.AdrType.Recv);  // Адрес доставки
            laoDS.TrdDoc.SetInteger("GplAdr", (rcdAdr == 0) ? 1 : rcdAdr);

            //_laoTrdDoc.SetInteger("VoCd", 1);                     // Вид оплаты 0-нал 1-безнал
            //_laoTrdDoc.SetInteger("OsnTp", 2);                    // Основание отсутствует
            //laoDS.TrdDoc.SetInteger("MarID", row.IS_RcdRoute);    // Rcd маршрута
            //laoDS.TrdDoc.Set("RcvrNm", row.IS_KagNm);             // Краткое название грузополучателя

            // Заполним код склада. Условность - код склада равен коду контрагента.
            laoDS.TrdDoc.SetInteger("StorCd", laoDS.Stor.GetRcdByCode(kagPrm.Cd));

            laoDS.TrdDoc.SetInteger("TaxMdRcd", laoDS.TaxPrm.TaxModelRcd);    // Rcd налоговой модели 

            laoDS.TrdDoc.SetInteger("Ud", docPrm.Ud);             // Условие доставки  0-сам 1-центр
            laoDS.TrdDoc.SetInteger("ValCd", 1);                  // Валюта
            laoDS.TrdDoc.SetInteger("ValKotCd", 1);               // Котировка
            laoDS.TrdDoc.SetInteger("ValKur", 1);                 // Курс
            #endregion ["Заполнение шапки документа"]

            #region ["========================= Заполнение спецификации ============================"]
            decimal Sum = 0, SumTax = 0, SumTotal = 0, TotQt = 0;

            // Перебор строк спецификации данного документа
            foreach (InputDocDS.DocDetailRow rspc in row.GetChildRows("FK_DocHeader_DocDetail"))
            {
                laoDS.TrdSpc.Append();

                PricePrm prcPrm = GetPosPricePrm(row, rspc, cddoc, kagPrm);
                laoDS.TrdSpc.SetInteger("CdPrc", prcPrm.RcdPrice);      // Rcd прейскуранта
                laoDS.TrdSpc.SetInteger("RcdPrc", prcPrm.RcdPosPrc);    // Rcd позиции прейскуранта
                laoDS.TrdSpc.SetDate("Dat", row.date_doc);              // Дата отгрузки

                int RcdEi = laoDS.Nom.GetEiRcdByCode(rspc.ei);
                laoDS.TrdSpc.SetInteger("EiCn", RcdEi);                 // Rcd ЕИ цены 
                laoDS.TrdSpc.SetInteger("EiQt", RcdEi);                 // Rcd ЕИ количества

                KagPrm gplPrm = laoDS.KAG.GetPrmByCode(row.gpl_cd);
                laoDS.TrdSpc.SetInteger("KAgRcd", gplPrm.RcdPlat);      // Rcd плательщика
                TotQt += rspc.qt;                                       // Накапливаем итоговое количество (килограммы с километрами)
                // Количество хранится строкой с десятичной точкой.
                string sqt = rspc.qt.ToString().Replace(',', '.');
                laoDS.TrdSpc.Set("Qt", sqt);                            // Количество
                laoDS.TrdSpc.Set("QtCn", sqt);                          // Количество
                laoDS.TrdSpc.SetInteger("RcdAdr", 1);                   // Rcd адреса

                NomPrm nomPrm = laoDS.Nom.GetNomPrm(rspc.nom_cd, false);
                laoDS.TrdSpc.SetInteger("RcdNom", nomPrm.Rcd);          // Rcd номенклатуры

                // Определим количество в основной единице
                decimal qtOsn = LaoNom.CalculateToOsn(nomPrm, RcdEi, rspc.qt);
                sqt = qtOsn.ToString().Replace(',', '.');
                laoDS.TrdSpc.Set("QtOsn", sqt);  // Количество в основной единице
                // Определим вес брутто/нетто
                string brut, nett;
                LaoNom.CalcBrutNett(nomPrm, RcdEi, rspc.qt, out brut, out nett); 
                laoDS.TrdSpc.Set("QtBrut", brut);
                laoDS.TrdSpc.Set("QtNett", nett);

                #region ["Считаем сумму и налоги по позиции"]
                decimal sumpos, taxsumpos, sumpostotal;
                decimal cenaEI = 0;     // Цена за единицу измерения количества
                cenaEI = LaoNom.ReCalcPriceEi(nomPrm, prcPrm.RcdEiPrc, RcdEi, prcPrm.Cena);

                sumpos = Math.Round(cenaEI * rspc.qt, 2, MidpointRounding.AwayFromZero);
                // НДС
                switch (laoDS.TaxPrm.TaxClcMode)
                {
                    case TaxCalcMode.Add:           // Ндс сверху
                        {
                            if (cddoc == CdDoc.IncomBill)   // Расчет для приходной накладной
                            {
                                sumpostotal = rspc.nom_sum; // Для приходной накладной сумма по позиции берется из входного документа
                                taxsumpos = Math.Round(sumpostotal * laoDS.TaxPrm.TaxRateValue / (100 + laoDS.TaxPrm.TaxRateValue), 2, MidpointRounding.AwayFromZero);
                                sumpos = sumpostotal - taxsumpos;
                            }
                            else                            // Для остальных документов
                            {
                                taxsumpos = Math.Round(sumpos * laoDS.TaxPrm.TaxRateValue / 100, 2, MidpointRounding.AwayFromZero);
                                sumpostotal = sumpos + taxsumpos;
                            }
                            break;
                        }
                    default:                                // Ндс в том числе или без НДС
                        {
                            if (cddoc == CdDoc.IncomBill)   // Расчет для приходной накладной   
                                sumpos = rspc.nom_sum;      // Сумма всего и она же - по позиции берется из входного документа

                            sumpostotal = sumpos;
                            taxsumpos = Math.Round(sumpostotal * laoDS.TaxPrm.TaxRateValue / (100 + laoDS.TaxPrm.TaxRateValue), 2, MidpointRounding.AwayFromZero);
                            break;
                        }
                }
                #endregion ["Считаем сумму и налоги по позиции"]

                laoDS.TrdSpc.Set("Sum", sumpos);
                laoDS.TrdSpc.Set("SumExt", sumpostotal);
                laoDS.TrdSpc.Set("SumOpl", sumpostotal);
                laoDS.TrdSpc.Set("SumSkl", sumpostotal);
                laoDS.TrdSpc.Set("SumTax", taxsumpos);
                laoDS.TrdSpc.Set("SumTSk", -taxsumpos);

                // Для приходных документов (возврат от покупателя и приход от поставщика) 
                // нужно посчитать цену и сумму оприходования. Это цена и сумма без НДС.
                // Сюда же - возврат от покупателя
                decimal sumseb = 0;
                if (cddoc == CdDoc.IncomBill || cddoc == CdDoc.ReversOutcomBill)
                {
                    if (laoDS.TaxPrm.TaxClcMode == TaxCalcMode.Add)
                    {
                        // НДС начисляется
                        //laoDS.TrdSpc.Set("CnSeb", prcPrm.Cena);
                        sumseb = sumpos;
                        laoDS.TrdSpc.Set("SumSeb", sumseb);
                    }
                    else
                    {
                        // НДС извлекается
                        decimal cn = Math.Round(prcPrm.Cena * 100 / (100 + laoDS.TaxPrm.TaxRateValue), 2, MidpointRounding.AwayFromZero);
                        sumseb = Math.Round(sumpos * 100 / (100 + laoDS.TaxPrm.TaxRateValue), 2, MidpointRounding.AwayFromZero);
                        //laoDS.TrdSpc.Set("CnSeb", cn);
                        laoDS.TrdSpc.Set("SumSeb", sumseb);
                    }
                }
                
                // Определим цену в основной единице измерения
                decimal cnOsn = 0;
                decimal cnSeb = 0;
                if (qtOsn != 0)
                {
                    // Для приходных документов точность 6 знаков, чтобы избежать погрешности округления при формировании ордера.
                    // Для остальных документов - 2 знака.
                    int rounding = 2;
                    if(cddoc == CdDoc.IncomBill || cddoc == CdDoc.ReversOutcomBill)
                        rounding = 6;
                    cnOsn = Math.Round(sumpostotal / qtOsn, rounding,MidpointRounding.AwayFromZero);
                    cnSeb = Math.Round(sumseb / qtOsn, rounding, MidpointRounding.AwayFromZero);
                }
                // Для накладной на возврат от покупателя цена и сумма по себестоимости определяются по прейскуранту
                // закупочных цен, как для возврата поставщику.
                if (cddoc == CdDoc.ReversOutcomBill)    
                {
                    // Переопределим посчитанные ранее цену и сумму
                    PricePrm prcPrmRev = GetPosPricePrm(row, rspc, CdDoc.ReversIncomBill, kagPrm);
                    // Определим цену за основную единицу измерения количества
                    cnSeb = LaoNom.ReCalcPriceEi(nomPrm, prcPrmRev.RcdEiPrc, nomPrm.RcdOsnEi, prcPrmRev.Cena);
                    sumseb = qtOsn * cnSeb;
                    laoDS.TrdSpc.Set("SumSeb", sumseb);
                }

                //        Значения цен в полях в зависимости от вида документа
                //  Поле      ПрНакл    ВозврПост       РасхНакл    ВозврОтПокуп
                //  Cn        ЕдДок       ЕдДок          ЕдДок        ЕдДок 
                //  CnExt     ОснЕд       ОснЕд          ОснЕд        Скл 
                //  CnFul       0         ОснЕд          ЕдДок          0 
                //  CnSeb     ОснЕд       ОснЕд            0          Скл
                //  CnSkl     ЕдДок       ЕдДок          ЕдДок        ЕдДок
                //
                //  ЕдДок  - цена за количество в единице документа
                //  ОснЕд  - Цена за количество в основной единице
                //  Скл    - Цена оприходования на склад за осн.ед
                //  В РасхНакл CnFul - это цена из прейскуранта.
                laoDS.TrdSpc.Set("Cn", cenaEI);                         // Цена за единицу количества в ед. документа
                laoDS.TrdSpc.Set("CnExt", cnOsn);                       // Цена
                if(cddoc == CdDoc.ReversIncomBill)
                    laoDS.TrdSpc.Set("CnFul", cnOsn);                   // Цена
                if (cddoc == CdDoc.OutcomBill)
                    laoDS.TrdSpc.Set("CnFul", prcPrm.Cena);             // Цена

                if (cddoc == CdDoc.ReversOutcomBill)
                    laoDS.TrdSpc.Set("CnSkl", cnSeb);                   // Цена
                else
                    laoDS.TrdSpc.Set("CnSkl", prcPrm.Cena);             // Цена
                
                laoDS.TrdSpc.Set("CnSeb", cnSeb);


                int dest = (cddoc == CdDoc.IncomBill || cddoc == CdDoc.OutcomBill) ? 2 : 0;
                laoDS.TrdSpc.SetInteger("Dest", dest);                 // Источник записи ?
                int tpcn = (cddoc == CdDoc.IncomBill || cddoc == CdDoc.OutcomBill) ? 1 : 0;
                laoDS.TrdSpc.SetInteger("TpCn", tpcn);                    // Тип цены ?
                laoDS.TrdSpc.SetInteger("TypHdr", 1);                 // Тип заголовка ?
                laoDS.TrdSpc.SetInteger("ValCd", 1);                   // Код валюты
                laoDS.TrdSpc.SetInteger("ValKur", 1);                  // Курс валюты

                // Накапливаем суммы по документу
                Sum += sumpos;
                SumTax += taxsumpos;
                SumTotal += sumpostotal;

                #region ["Создание налоговой записи"]
                if (laoDS.TaxPrm.TaxClcMode != TaxCalcMode.Absent)
                {
                    laoDS.SpcTax.Append();
                    laoDS.SpcTax.SetInteger("TaxCd", nomPrm.TaxCd);             // Код налога
                    laoDS.SpcTax.SetInteger("TaxRateCd", nomPrm.TaxRateCd);     // Код ставки налога
                    laoDS.SpcTax.Set("TaxSum", taxsumpos);
                    if (laoDS.TaxPrm.TaxClcMode == TaxCalcMode.Extract)
                    {
                        laoDS.SpcTax.SetInteger("TaxFlg", 4);       // Налог извлекается
                        laoDS.SpcTax.Set("TaxSumBas", sumpostotal);
                    }
                    else
                    {
                        laoDS.SpcTax.SetInteger("TaxFlg", 0);       // Налог начисляется
                        laoDS.SpcTax.Set("TaxSumBas", sumpos);
                    }
                    laoDS.SpcTax.Post();
                }
                #endregion ["Создание налоговой записи"]
                laoDS.TrdSpc.Post();
            }
            #endregion ["========================= Заполнение спецификации ============================"]

            // Запишем итоговые суммы в документ
            laoDS.TrdDoc.Set("Sum", SumTotal);                   // Итоговая сумма по документу
            laoDS.TrdDoc.Set("SumNds", SumTax);                  // Сумма НДС
            laoDS.TrdDoc.Set("SumTax", SumTax);                  // Сумма налогов
            laoDS.TrdDoc.Set("SumWoNds", SumTotal - SumTax);     // Сумма без НДС
            laoDS.TrdDoc.Set("TotQt", TotQt.ToString().Replace(',', '.'));  // Итоговое количество

            laoDS.TrdDoc.Post();

        }

        /// <summary> Получение параметров цены и прейскуранта.
        /// В зависимости от типа документа определяем прейскурант.
        /// Для приходной накладной прейскурант не определяем. 
        /// Для накладной на возврат поставщику - поиск по списку прейскурантов
        /// Для расходной и возвратной накладных реализации - определяем прейскурант из настроек 
        /// контрагента, а если он не указан - из настроек программы.
        /// </summary>
        /// <param name="row"></param>
        /// <param name="rspc"></param>
        /// <param name="cddoc"></param>
        /// <returns></returns>
        private PricePrm GetPosPricePrm(InputDocDS.DocHeaderRow row, InputDocDS.DocDetailRow rspc, CdDoc cddoc, KagPrm kagPrm)
        {
            PricePrm prcPrm = new PricePrm();
            
            #region ["Приходная накладная"]
		    if (cddoc == CdDoc.IncomBill)   // Приходная накладная
            {
                if( rspc.qt == 0)           // Количество равно нулю - цену посчитать не сможем
                    return prcPrm;
                // Для приходной накладной прайса нет - имитируем его.
                prcPrm.RcdEiPrc = laoDS.Nom.GetEiRcdByCode(rspc.ei);
                if(laoDS.TaxPrm.TaxClcMode == TaxCalcMode.Add)  // НДС начисляется сверху
                {  
                    // Выделим сумму без НДС, а из нее получим цену без НДС
                    prcPrm.Cena = Math.Round(Math.Round(rspc.nom_sum * 100 /(100 + laoDS.TaxPrm.TaxRateValue), 2, MidpointRounding.AwayFromZero) / rspc.qt, 2, MidpointRounding.AwayFromZero);
                }
                else
                {
                    prcPrm.Cena = Math.Round(rspc.nom_sum / rspc.qt, 2, MidpointRounding.AwayFromZero);
                }
                return prcPrm;
            }
	        #endregion ["Приходная накладная"]

            #region ["Накладные сбыта"]
            if( cddoc == CdDoc.OutcomBill || cddoc == CdDoc.ReversOutcomBill )  // Накладные сбыта
            {
                if (kagPrm.RcdPrice != 0)  // Указан индивидуальный прейскурант грузополучателя
                    return laoDS.Price.GetPrice(kagPrm.RcdPrice, rspc.nom_cd, row.date_doc);
                else
                    return laoDS.Price.GetPrice(DefaulPriceRcd, rspc.nom_cd, row.date_doc);  // Прейскурант по умолчанию
            }
            #endregion ["Накладные сбыта"]

            #region ["Возврат поставщику"]
            if (cddoc == CdDoc.ReversIncomBill) // Возврат поставщику
            {
                // Перебираем прейскуранты и ищем в них цену для артикула. Как только цена найдена, поиск прекращаем.
                foreach (int prcrcd in DefaultBuyPriceRcd)
                {
                    prcPrm = laoDS.Price.GetPrice(prcrcd, rspc.nom_cd, row.date_doc);
                    if (prcPrm.Cena != 0)
                        break;                  // Цену нашли, поиск прекращаем
                }
                return prcPrm;
            }
            #endregion ["Возврат поставщику"]

            return prcPrm;
        }

        /// <summary> Класс определяет набор константных параметров для торговых документов.
        /// </summary>
        public class LaoTradeDocPrm
        {

            /// <summary> Код действия
            /// </summary>
            public int Cd_Act
            {
                get { return _cd_Act; }
            }
            private int _cd_Act = 0;

            /// <summary> Код документа
            /// </summary>
            public int Cd_Doc
            {
                get { return _cd_Doc; }
            }
            private int _cd_Doc = 0;

            /// <summary> Направление движения
            /// </summary>
            public int Mov
            {
                get { return _mov; }
            }
            private int _mov = 0;

            /// <summary> Тип чего-то
            /// </summary>
            public int Type
            {
                get { return _type; }
            }
            private int _type = 0;

            /// <summary> Статус
            /// </summary>
            public int Stt
            {
                get { return _stt; }
            }
            private int _stt = 0;

            /// <summary> Условие доставки 0-сам 1-центр
            /// </summary>
            public int Ud
            {
                get { return _ud; }
            }
            private int _ud = 0;

            public CdDoc cdDoc = CdDoc.unknow;

            public LaoTradeDocPrm(CdDoc cddoc)
            {
                cdDoc = cddoc;

                switch (cddoc)
                {
                    case CdDoc.IncomBill:   // Приход от поставщика
                        _cd_Act = (int)Transfer.CdAct.IncomBill;
                        _cd_Doc = (int)Transfer.CdDoc.IncomBill;
                        _mov = (int)Transfer.Mov.Income;
                        _type = (int)Transfer.DocType.IncomBill;
                        _stt = (int)Transfer.Stt.IncomBill;
                        _ud = (int)Transfer.Ud.Himself;
                        break;
                    case CdDoc.OutcomBill:
                        _cd_Act = (int)Transfer.CdAct.OutcomBill;
                        _cd_Doc = (int)Transfer.CdDoc.OutcomBill;
                        _mov = (int)Transfer.Mov.Expenditure;
                        _type = (int)Transfer.DocType.OutcomBill;
                        _stt = (int)Transfer.Stt.OutcomBill;
                        _ud = (int)Transfer.Ud.Himself;
                        break;
                    case CdDoc.ReversIncomBill:
                        _cd_Act = (int)Transfer.CdAct.ReversIncomBill;
                        _cd_Doc = (int)Transfer.CdDoc.ReversIncomBill;
                        _mov = (int)Transfer.Mov.Expenditure;
                        _type = (int)Transfer.DocType.ReversIncomBill;
                        _stt = (int)Transfer.Stt.ReversIncomBill;
                        _ud = (int)Transfer.Ud.Himself;
                        break;
                    case CdDoc.ReversOutcomBill:
                        _cd_Act = (int)Transfer.CdAct.ReversOutcomBill;
                        _cd_Doc = (int)Transfer.CdDoc.ReversOutcomBill;
                        _mov = (int)Transfer.Mov.Income;
                        _type = (int)Transfer.DocType.ReversOutcomBill;
                        _stt = (int)Transfer.Stt.ReversOutcomBill;
                        _ud = (int)Transfer.Ud.Himself;
                        break;
                }               
            }
        }
    }
}
